<?php
// +----------------------------------------------------------------------
// | yogurt
// +----------------------------------------------------------------------
// +----------------------------------------------------------------------
// | Author: fengyi <1719847255.qq.com>
// +----------------------------------------------------------------------
namespace yogurt\template;

use yogurt\Config;
use yogurt\Request;

/**
 * 模板编译工具类
 * Class Compile
 * @package yogurt\template
 */
class Compile
{

    /**
     * 带编译文件
     * @var $template
     */
    private $template;
    /**
     * 需要替换的文本
     * @var false|string $content
     */
    private $content;
    /**
     * 编译后的文件
     * @var $compileFile
     */
    private $compileFile;
    /**
     * 左界定符
     * @var string $left
     */
    private $left = '\{';
    /**
     * 右界定符
     * @var string $right
     */
    private $right = '\}';
    /**
     * 替换前数组
     * @var array $Y_P
     */
    private $Y_P = array();
    /**
     * 替换后数组
     * @var array $Y_R
     */
    private $Y_R = array();

    /**
     * @throws \yogurt\Exception
     */
    public function __construct($template, $compileFile)
    {
        $this->getDivision();
        $this->template = $template;
        $this->compileFile = $compileFile;
        $this->content = file_get_contents($this->template);
        // 是否支持原生php
        if (Config::get('view.php_turn') === false) {
            $this->Y_P[] = "/<\?(=|php|)(.+?)\?>/is";
            $this->Y_R[] = "&lt;? \\1\\2? &gt";
        }

        //----------- 替换前部分 ---------------------

        //{$var}
        $this->Y_P[] = "/" . $this->left . "\\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)" . $this->right . "/";

        //{$var['var']}
        $this->Y_P[] = "/" . $this->left . "\\$(\w*[a-zA-Z0-9_])\[\'(\w*[a-zA-Z0-9])\'\]" . $this->right . "/";
        //{$var[var]}
        $this->Y_P[] = "/" . $this->left . "\\$(\w*[a-zA-Z0-9_]).*\[(\w*[a-zA-Z0-9])\]" . $this->right . "/";

        //{foreach $b item="$vo"}或者{loop $b item="$vo"}
        $this->Y_P[] = "/" . $this->left . "(loop|foreach) \\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)?\s+item.*\=.*\\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*).*" . $this->right . "/i";

        //{foreach $b as $key=>$vo}
        $this->Y_P[] = "/" . $this->left . "(foreach) \\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*).*as.*\\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*).*=>.*\\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)" . $this->right . "/i";

        //{volist $b name="list" id="vo"} 或者 {loop $b name="list" id="vo"}
        $this->Y_P[] = "/" . $this->left . "(loop|volist)?\s+name.*=.*\\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*).*?\s+id.*=.*(\"|\')(\w*[a-zA-Z0-9])(\"|')" . $this->right . "/i";

        //{/foreach}或者{\loop}或者{\if}
        $this->Y_P[] = "/" . $this->left . "\/(loop|foreach|if|volist)" . $this->right . "/i";

        //{if (condition)}
        $this->Y_P[] = "/" . $this->left . "if (.* ?)" . $this->right . "/i";

        //{(else if | elseif)}
        $this->Y_P[] = "/" . $this->left . "(else if|elseif) (.* ?)" . $this->right . "/i";

        //{else}
        $this->Y_P[] = "/" . $this->left . "else" . $this->right . "/i";

        //{#...# 或者 *...#，注释}
        $this->Y_P[] = "/" . $this->left . "(\#|\*)(.* ?)(\#|\*)" . $this->right . "/";


        //----------- 替换后部分 ---------------------

        //{$var}
        $this->Y_R[] = "<?php echo \\$\\1; ?>";
        //{$var['var']}
        $this->Y_R[] = "<?php echo \\$\\1['\\2']; ?>";
        //{$var[var]}
        $this->Y_R[] = "<?php echo \\$\\1[\\2]; ?>";
        //{foreach $b item="$vo"}或者{loop $b item="$vo"}
        $this->Y_R[] = "<?php foreach ((array)\\$\\2 as \\$\\3) { ?>";
        //{foreach $b as $key=>$vo}
        $this->Y_R[] = "<?php foreach ((array)\\$\\2 as \\$\\3 => \\$\\4) { ?>";
        //{volist $b name="list" id="vo"} 或者 {loop $b name="list" id="vo"}
        $this->Y_R[] = "<?php foreach ((array)\\$\\2 as \\$\\4) { ?>";
        //{/foreach}或者{\loop}或者{\if}
        $this->Y_R[] = "<?php } ?>";
        //{if (condition)}
        $this->Y_R[] = "<?php if (\\1) { ?>";
        //{(else if | elseif)}
        $this->Y_R[] = "<?php }else if (\\2) { ?>";
        //{else}
        $this->Y_R[] = "<?php }else{ ?>";
        //{#...# 或者 *...#，注释}
        $this->Y_R[] = "";
    }

    /**
     * 获取标签开始结束符号
     * @return void
     * @throws \yogurt\Exception
     */
    public function getDivision()
    {
        $left = Config::get('view.taglib_begin');
        $right = Config::get('view.taglib_end');
        if (!empty($left)) {
            $this->left = '\\' . $left;
        }
        if (!empty($right)) {
            $this->right = '\\' . $right;
        }
    }

    /**
     * 生成模版文件
     * @return void
     */
    public function compile()
    {
        $this->replaceContent();
        $this->replaceStaticFile();
        // 模版继承替换
        $this->replaceExtends();
        // 解析include
        $this->includeStaticFile();
        file_put_contents($this->compileFile, $this->content);
    }

    /**
     * 替换字符
     * @return void
     */
    public function replaceContent()
    {
        $this->content = preg_replace($this->Y_P, $this->Y_R, $this->content);
    }

    /**
     * 模版继承替换
     */
    public function replaceExtends()
    {
        // 判断是否匹配到模版继承
        if (!preg_match('/^\{(\s*)extend(\s+)name="([.A-Za-z0-9_\/]+)"(\s+)\/\}/ui', $this->content, $matchesContent)) {
            return;
        }

        // 获取父模版全路径
        $baseTemplate = APP_PATH . Request::model() . DIRECTORY_SEPARATOR . Config::get('app.view_directory') . DIRECTORY_SEPARATOR . $matchesContent[3] . '.' . Config::get('app.default_template_exit');
        // 父模版内容
        $baseContent = file_get_contents($baseTemplate);

        // 匹配父模版block
        if (!preg_match_all('/\{block name="([A-Za-z0-9_]+)"\}([\s\S]*?)\{\/block\}/ui', $baseContent, $matchesBaseBlock)) {
            return;
        }

        // 匹配子模版block
        if (!preg_match_all('/\{block name="([A-Za-z0-9_]+)"\}([\s\S]*?)\{\/block\}/ui', $this->content, $matchesChildBlock)) {
            return;
        }

        // 父模版块内容
        $replaceBaseContent = [];
        // 子模版块内容
        $replaceChildContent = [];

        foreach ($matchesBaseBlock[0] as $value) {
            // 匹配父模版block
            if (!preg_match('/\{block name="([A-Za-z0-9_]+)"\}([\s\S]*?)\{\/block\}/ui', $value, $subMatchesBlock)) {
                continue;
            }
            $replaceBaseContent[$subMatchesBlock[1]] = $subMatchesBlock[2];
        }

        foreach ($matchesChildBlock[0] as $value) {
            // 匹配子模版block
            if (!preg_match('/\{block name="([A-Za-z0-9_]+)"\}([\s\S]*?)\{\/block\}/ui', $value, $subMatchesBlock)) {
                continue;
            }
            $replaceChildContent[$subMatchesBlock[1]] = $subMatchesBlock[2];
        }

        // 进行父模版替换
        foreach ($replaceBaseContent as $key => $value) {
            $subContent = $replaceChildContent[$key] ?? '';
            $baseContent = preg_replace('/\{block name="' . $key . '"\}([\s\S]*?)\{\/block\}/ui', $subContent, $baseContent);
        }
        $this->content = $baseContent;
    }

    /**
     * 解析静态页面引入
     */
    public function includeStaticFile()
    {
        if (!preg_match('/\{(\s*)include(\s+)file="([.A-Za-z0-9_\/]+)"(\s+)\/\}/ui', $this->content, $matchesFile)) {
            return;
        }
        // 获取父模版全路径
        $includeFile = APP_PATH . Request::model() . DIRECTORY_SEPARATOR . Config::get('app.view_directory') . DIRECTORY_SEPARATOR . $matchesFile[3] . '.' . Config::get('app.default_template_exit');
        // 父模版内容
        $fileContent = file_get_contents($includeFile);

        $this->content = preg_replace('/\{(\s*)include(\s+)file="([.A-Za-z0-9_\/]+)"(\s+)\/\}/ui', $fileContent, $this->content);
    }

    /**
     * 加入对CSS|JavaScript文件的解析
     * @return void [type] [description]
     */
    public function replaceStaticFile()
    {
        $this->content = preg_replace('/\{\!css(.* ?)\!\}/', '<link href=\\1 >', $this->content);
        $this->content = preg_replace('/\{\!js(.* ?)\!\}/', '<script src=\\1 ></script>', $this->content);
    }

    public function __set($name, $value)
    {
        $this->$name = $value;
    }

    public function __get($name)
    {
        return $this->$name ?? null;
    }
}